﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace CSharpExperiments.Metaprogramming
{
	public static class MetaGeneralExtensions
	{
		private static BindingFlags flags = BindingFlags.Default
			| BindingFlags.Static
			| BindingFlags.Instance
			| BindingFlags.NonPublic
			| BindingFlags.Public
			| BindingFlags.FlattenHierarchy;

		public static Z Do<Z>(this object obj, String methodName, params Object[] args)
		{
			return (Z) obj.GetType().GetMethod(methodName, args.Select(x => x.GetType()).ToArray()).Invoke(obj, args.ToArray());
		}
		
		public static void Do(this object obj, String methodName, params Object[] args)
		{
			obj.GetType().GetMethod(methodName, args.Select(x => x.GetType()).ToArray()).Invoke(obj, args);
		}

		public static Func<A> GetFunc<A>(this object obj, String methodName)
		{
			return (Func<A>) Delegate.CreateDelegate(typeof(Func<A>), obj, obj.GetType().GetMethod(methodName, new Type[0]));
		}
		
		public static Func<A, B> GetFunc<A, B>(this object obj, String methodName)
		{
			return (Func<A, B>) Delegate.CreateDelegate(typeof(Func<A, B>), obj, obj.GetType().GetMethod(methodName, new []{typeof(A)}));
		}
		
		public static Func<A, B, C> GetFunc<A, B, C>(this object obj, String methodName)
		{
			return (Func<A, B, C>) Delegate.CreateDelegate(typeof(Func<A, B, C>), obj, obj.GetType().GetMethod(methodName, new []{typeof(A), typeof(B)}));
		}
		
		public static Func<A, B, C, D> GetFunc<A, B, C, D>(this object obj, String methodName)
		{
			return (Func<A, B, C, D>) Delegate.CreateDelegate(typeof(Func<A, B, C, D>), obj, obj.GetType().GetMethod(methodName, new []{typeof(A), typeof(B), typeof(C)}));
		}
		
		public static Func<A, B, C, D, E> GetFunc<A, B, C, D, E>(this object obj, String methodName)
		{
			return (Func<A, B, C, D, E>) Delegate.CreateDelegate(typeof(Func<A, B, C, D, E>), obj, obj.GetType().GetMethod(methodName, new []{typeof(A), typeof(B), typeof(C), typeof(D)}));
		}
		
		public static Func<A, B, C, D, E, F> GetFunc<A, B, C, D, E, F>(this object obj, String methodName)
		{
			return (Func<A, B, C, D, E, F>) Delegate.CreateDelegate(typeof(Func<A, B, C, D, E, F>), obj, obj.GetType().GetMethod(methodName, new []{typeof(A), typeof(B), typeof(C), typeof(D), typeof(E)}));
		}
		
		public static Action GetAction(this Object obj, String methodName)
		{
			return (Action) Delegate.CreateDelegate(typeof(Action), obj, obj.GetType().GetMethod(methodName, new Type[0]));
		}
		
		public static Action<A> GetAction<A>(this Object obj, String methodName)
		{
			return (Action<A>) Delegate.CreateDelegate(typeof(Action<A>), obj, obj.GetType().GetMethod(methodName, new []{typeof(A)}));
		}
		
		public static Action<A, B> GetAction<A, B>(this Object obj, String methodName)
		{
			return (Action<A, B>) Delegate.CreateDelegate(typeof(Action<A, B>), obj, obj.GetType().GetMethod(methodName, new []{typeof(A), typeof(B)}));
		}
		
		public static Action<A, B, C> GetAction<A, B, C>(this Object obj, String methodName)
		{
			return (Action<A, B, C>) Delegate.CreateDelegate(typeof(Action<A, B, C>), obj, obj.GetType().GetMethod(methodName, new []{typeof(A), typeof(B), typeof(C)}));
		}

		public static Action<A, B, C, D> GetAction<A, B, C, D>(this Object obj, String methodName)
		{
			return (Action<A, B, C, D>) Delegate.CreateDelegate(typeof(Action<A, B, C, D>), obj, obj.GetType().GetMethod(methodName, new []{typeof(A), typeof(B), typeof(C), typeof(D)}));
		}

		public static Action<A, B, C, D, E> GetAction<A, B, C, D, E>(this Object obj, String methodName)
		{
			return (Action<A, B, C, D, E>) Delegate.CreateDelegate(typeof(Action<A, B, C, D, E>), obj, obj.GetType().GetMethod(methodName, new []{typeof(A), typeof(B), typeof(C), typeof(D), typeof(E)}));
		}

		public static bool HasMethod(this Object obj, String methodName, params Type[] types)
		{
			return obj != null && obj.GetType().GetMethod(methodName, types) != null;
		}
		
		public static PropertyInfo[] Props(this Object obj)
		{
			return obj.GetType().GetProperties();
		}

		public static PropertyInfo[] PropsWithAttr<T>(this Object obj) where T : Attribute
		{
			return obj.GetType().GetProperties().Where(HasAttr<T>).ToArray();
		}

		public static T Prop<T>(this Object obj, String propName)
		{
			return (T) obj.GetType().GetProperty(propName, flags).GetValue(obj, null);
		}
		
		public static T Prop<T>(this Object obj, String propName, T value)
		{
			obj.GetType().GetProperty(propName, flags).SetValue(obj, value, null);
			return value;
		}

		public static bool HasProp(this Object obj, String propName)
		{
			return obj != null && obj.GetType().GetProperty(propName, flags) != null;
		}
		
		public static T Attr<T>(this Object obj) where T : Attribute
		{
			return Attr<T>(obj.GetType());
		}

		public static List<T> Attrs<T>(this Object obj) where T : Attribute
		{
			return Attrs<T>(obj.GetType());
		}

		public static bool HasAttr<T>(this Object obj) where T : Attribute
		{
			return HasAttr<T>(obj.GetType());
		}
	}
}
